// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ld

import (
	"cmd/internal/goobj"
	"cmd/internal/objabi"
	"cmd/internal/sys"
	"cmd/link/internal/loader"
	"cmd/link/internal/sym"
	"fmt"
	"internal/buildcfg"
	"os"
	"path/filepath"
	"strings"
)

const funcSize = 10 * 4 // funcSize是运行时/运行时2中_func对象的大小。go 

// pclntab保存生成pclntab所需的状态。
type pclntab struct {
	// 找到的第一个和最后一个函数。
	firstFunc, lastFunc loader.Sym

	// 运行pclntab的总大小。
	size int64

	// 运行时。pclntab的符号
	carrier     loader.Sym
	pclntab     loader.Sym
	pcheader    loader.Sym
	funcnametab loader.Sym
	findfunctab loader.Sym
	cutab       loader.Sym
	filetab     loader.Sym
	pctab       loader.Sym

	// 函数数+文本节数-1。这是一个意外的值，因为具有多个文本节的平台可以在这些区域中插入一个伪函数，因为外部链接器可以在这些区域中放置
	// 函数。我们将这些区域标记为Go 
	// 运行时未涵盖。
	// 
	// 在大多数平台上，这是可访问函数的数量。
	nfunc int32

	// 运行时的文件名数。文件标签。
	nfiles uint32
}

// addGeneratedSym将生成器符号添加到pclntab，并返回新的符号。
// 呼叫方有责任将符号保存在状态中。
func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f generatorFunc) loader.Sym {
	size = Rnd(size, int64(ctxt.Arch.PtrSize))
	state.size += size
	s := ctxt.createGeneratorSymbol(name, 0, sym.SPCLNTAB, size, f)
	ctxt.loader.SetAttrReachable(s, true)
	ctxt.loader.SetCarrierSym(s, state.carrier)
	ctxt.loader.SetAttrNotInSymbolTable(s, true)
	return s
}

// makePclntab生成一个pclntab对象，并组装所有编译单元
// 我们需要编写pclntab。返回pclntab结构、
// 编译单元的一部分，以及生成pclntab所需的函数符号的一部分。
func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit, []loader.Sym) {
	ldr := ctxt.loader
	state := new(pclntab)

	// 收集一些基本的统计数据和信息。
	seenCUs := make(map[*sym.CompilationUnit]struct{})
	compUnits := []*sym.CompilationUnit{}
	funcs := []loader.Sym{}

	for _, s := range ctxt.Textp {
		if !emitPcln(ctxt, s, container) {
			continue
		}
		funcs = append(funcs, s)
		state.nfunc++
		if state.firstFunc == 0 {
			state.firstFunc = s
		}
		state.lastFunc = s

		// 我们需要跟踪我们看到的所有编译单元。有些符号
		// （例如go.buildid、_cgoexp_等）没有编译单元。
		cu := ldr.SymUnit(s)
		if _, ok := seenCUs[cu]; cu != nil && !ok {
			seenCUs[cu] = struct{}{}
			cu.PclnIndex = len(compUnits)
			compUnits = append(compUnits, cu)
		}
	}
	return state, compUnits, funcs
}

func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool {
	// 我们只想为“最低的
	// 级别”符号生成func表项，而不是子符号的容器。
	return !container.Has(s)
}

func computeDeferReturn(ctxt *Link, deferReturnSym, s loader.Sym) uint32 {
	ldr := ctxt.loader
	target := ctxt.Target
	deferreturn := uint32(0)
	lastWasmAddr := uint32(0)

	relocs := ldr.Relocs(s)
	for ri := 0; ri < relocs.Count(); ri++ {
		r := relocs.At(ri)
		if target.IsWasm() && r.Type() == objabi.R_ADDR {
			// wasm/ssa。go在延迟返回调用之前生成一个ARESUMEPOINT。
			// 延迟返回呼叫的“PC”存储在ARESUMEPOINT上的
			// R_ADDR relocation中。
			lastWasmAddr = uint32(r.Add())
		}
		if r.Type().IsDirectCall() && (r.Sym() == deferReturnSym || ldr.IsDeferReturnTramp(r.Sym())) {
			if target.IsWasm() {
				deferreturn = lastWasmAddr - 1
			} else {
				// 注意：重定位目标在call指令中，但
				// 不一定是整个指令（例如，在
				// x86上，重定位适用于5字节调用
				// 指令中的字节[1:5]）。
				deferreturn = uint32(r.Off())
				switch target.Arch.Family {
				case sys.AMD64, sys.I386:
					deferreturn--
				case sys.ARM, sys.ARM64, sys.MIPS, sys.MIPS64, sys.PPC64, sys.RISCV64:
					// 无更改
				case sys.S390X:
					deferreturn -= 2
				default:
					panic(fmt.Sprint("Unhandled architecture:", target.Arch.Family))
				}
			}
			break // 只需要一个
		}
	}
	return deferreturn
}

// genInlTreeSym为具有
// 指定FuncInfo的函数生成InlTree sym。
func genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, nameOffsets map[loader.Sym]uint32) loader.Sym {
	ldr := ctxt.loader
	its := ldr.CreateExtSym("", 0)
	inlTreeSym := ldr.MakeSymbolUpdater(its)
	// 注意：生成的符号是一种符号。SGOFUNC，作为一个
	// 信号发送到需要与
	// 其他类似符号（gcdata等）分组的symtab（）相位；dodata（）阶段将
	// 最终将类型切换回SRODATA。
	inlTreeSym.SetType(sym.SGOFUNC)
	ldr.SetAttrReachable(its, true)
	ldr.SetSymAlign(its, 4) // 它有32位字段
	ninl := fi.NumInlTree()
	for i := 0; i < int(ninl); i++ {
		call := fi.InlTree(i)
		val := call.File
		nameoff, ok := nameOffsets[call.Func]
		if !ok {
			panic("couldn't find function name offset")
		}

		inlTreeSym.SetUint16(arch, int64(i*20+0), uint16(call.Parent))
		inlFunc := ldr.FuncInfo(call.Func)

		var funcID objabi.FuncID
		if inlFunc.Valid() {
			funcID = inlFunc.FuncID()
		}
		inlTreeSym.SetUint8(arch, int64(i*20+2), uint8(funcID))

		// 字节3未使用
		inlTreeSym.SetUint32(arch, int64(i*20+4), uint32(val))
		inlTreeSym.SetUint32(arch, int64(i*20+8), uint32(call.Line))
		inlTreeSym.SetUint32(arch, int64(i*20+12), uint32(nameoff))
		inlTreeSym.SetUint32(arch, int64(i*20+16), uint32(call.ParentPC))
	}
	return its
}

// makeInlSyms返回加载程序的映射。Sym中创建的Sym。
func makeInlSyms(ctxt *Link, funcs []loader.Sym, nameOffsets map[loader.Sym]uint32) map[loader.Sym]loader.Sym {
	ldr := ctxt.loader
	// 创建我们需要的内联符号。
	inlSyms := make(map[loader.Sym]loader.Sym)
	for _, s := range funcs {
		if fi := ldr.FuncInfo(s); fi.Valid() {
			fi.Preload()
			if fi.NumInlTree() > 0 {
				inlSyms[s] = genInlTreeSym(ctxt, ldr.SymUnit(s), fi, ctxt.Arch, nameOffsets)
			}
		}
	}
	return inlSyms
}

// generatePCHeader创建运行时。pcheader符号，将其设置为
// 生成器，以便稍后填充其数据。
func (state *pclntab) generatePCHeader(ctxt *Link) {
	ldr := ctxt.loader
	textStartOff := int64(8 + 2*ctxt.Arch.PtrSize)
	size := int64(8 + 8*ctxt.Arch.PtrSize)
	writeHeader := func(ctxt *Link, s loader.Sym) {
		header := ctxt.loader.MakeSymbolUpdater(s)

		writeSymOffset := func(off int64, ws loader.Sym) int64 {
			diff := ldr.SymValue(ws) - ldr.SymValue(s)
			if diff <= 0 {
				name := ldr.SymName(ws)
				panic(fmt.Sprintf("expected runtime.pcheader(%x) to be placed before %s(%x)", ldr.SymValue(s), name, ldr.SymValue(ws)))
			}
			return header.SetUintptr(ctxt.Arch, off, uintptr(diff))
		}

		// 写入头。
		// 与runtime/symtab保持同步。go:pcHeader和package debug/gosym。
		header.SetUint32(ctxt.Arch, 0, 0xfffffff0)
		header.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC))
		header.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize))
		off := header.SetUint(ctxt.Arch, 8, uint64(state.nfunc))
		off = header.SetUint(ctxt.Arch, off, uint64(state.nfiles))
		if off != textStartOff {
			panic(fmt.Sprintf("pcHeader textStartOff: %d != %d", off, textStartOff))
		}
		off += int64(ctxt.Arch.PtrSize) // 跳过runtimeText重新定位
		off = writeSymOffset(off, state.funcnametab)
		off = writeSymOffset(off, state.cutab)
		off = writeSymOffset(off, state.filetab)
		off = writeSymOffset(off, state.pctab)
		off = writeSymOffset(off, state.pclntab)
		if off != size {
			panic(fmt.Sprintf("pcHeader size: %d != %d", off, size))
		}
	}

	state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
	// 创建runtimeText重新定位。
	sb := ldr.MakeSymbolUpdater(state.pcheader)
	sb.SetAddr(ctxt.Arch, textStartOff, ldr.Lookup("runtime.text", 0))
}

// walkFuncs迭代函数，为每个唯一的
// 函数和内联函数调用一个函数。
func walkFuncs(ctxt *Link, funcs []loader.Sym, f func(loader.Sym)) {
	ldr := ctxt.loader
	seen := make(map[loader.Sym]struct{})
	for _, s := range funcs {
		if _, ok := seen[s]; !ok {
			f(s)
			seen[s] = struct{}{}
		}

		fi := ldr.FuncInfo(s)
		if !fi.Valid() {
			continue
		}
		fi.Preload()
		for i, ni := 0, fi.NumInlTree(); i < int(ni); i++ {
			call := fi.InlTree(i).Func
			if _, ok := seen[call]; !ok {
				f(call)
				seen[call] = struct{}{}
			}
		}
	}
}

// generateFuncnametab创建函数名表。在运行时返回
// func符号到名称偏移量的映射。funcnamtab。
func (state *pclntab) generateFuncnametab(ctxt *Link, funcs []loader.Sym) map[loader.Sym]uint32 {
	nameOffsets := make(map[loader.Sym]uint32, state.nfunc)

	// 运行时使用的名称是3个返回字符串的串联。
	// 对于常规函数，只有一个返回的字符串是非空的。
	// 对于通用函数，我们使用三个部分，以便将最外层“[]”内的所有内容打印为“…”。
	nameParts := func(name string) (string, string, string) {
		i := strings.IndexByte(name, '[')
		if i < 0 {
			return name, "", ""
		}
		// TODO:一旦引导编译器>=Go 1.5，就使用LastIndexByte。
		j := len(name) - 1
		for j > i && name[j] != ']' {
			j--
		}
		if j <= i {
			return name, "", ""
		}
		return name[:i], "[...]", name[j+1:]
	}

	// 写入以null结尾的字符串。
	writeFuncNameTab := func(ctxt *Link, s loader.Sym) {
		symtab := ctxt.loader.MakeSymbolUpdater(s)
		for s, off := range nameOffsets {
			a, b, c := nameParts(ctxt.loader.SymName(s))
			o := int64(off)
			o = symtab.AddStringAt(o, a)
			o = symtab.AddStringAt(o, b)
			_ = symtab.AddCStringAt(o, c)
		}
	}

	// 在CUs中循环，并计算所需的大小。
	var size int64
	walkFuncs(ctxt, funcs, func(s loader.Sym) {
		nameOffsets[s] = uint32(size)
		a, b, c := nameParts(ctxt.loader.SymName(s))
		size += int64(len(a) + len(b) + len(c) + 1) // NULL终止
	})

	state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab)
	return nameOffsets
}

// walkFilenames walks funcs，为每个
// 函数的行表中使用的每个文件名调用一个函数。
func walkFilenames(ctxt *Link, funcs []loader.Sym, f func(*sym.CompilationUnit, goobj.CUFileIndex)) {
	ldr := ctxt.loader

	// 循环所有函数，找到我们需要的文件名。
	for _, s := range funcs {
		fi := ldr.FuncInfo(s)
		if !fi.Valid() {
			continue
		}
		fi.Preload()

		cu := ldr.SymUnit(s)
		for i, nf := 0, int(fi.NumFile()); i < nf; i++ {
			f(cu, fi.File(i))
		}
		for i, ninl := 0, int(fi.NumInlTree()); i < ninl; i++ {
			call := fi.InlTree(i)
			f(cu, call.File)
		}
	}
}

// generateFilenameTabs创建文件名查找所需的LUT。返回每个CU在运行时开始的索引的片段
// 。库塔布。
// 
// 函数对象跟踪它们在打印堆栈时引用的文件。
// 如果CU[M]引用
// 文件[1-N]，此函数将创建每个CU的文件名列表，并生成以下内容：
// 
// 运行时。cutab:
// CU[M]
// /OFFSETOFILENAME[0]
// /OFFSETOFILENAME[1]
// 。。
// 
// 运行时。filetab 
// filename[0]
// filename[1]
// 
// 如果给定一个func，查找文件名就会变成：
// 0），文件名索引[K]
// 1）Get func。CUIndex:M:=func。cuOffset 
// 2）查找文件名偏移量：fileOffset:=运行时。cutab[M+K]
// 3）获取文件名：getcstring（runtime.filetab[fileOffset]）
func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, funcs []loader.Sym) []uint32 {
	// 。
	// 
	// 注意，我们将文件名存储在对象
	// 文件的单独部分中，并根据实际值进行重复数据消除。最好使用内容可寻址符号（和
	// 然后不加载额外的文件名）将文件名存储为符号，并使用
	// 符号名的哈希值进行编目。
	// 
	// TODO:将文件名存储为符号。（注意，如果您
	// 也将字符串移动到始终使用更大的内容可寻址哈希
	// 函数，并使用该哈希值进行唯一性测试，这将是最简单的。）
	cuEntries := make([]goobj.CUFileIndex, len(compUnits))
	fileOffsets := make(map[string]uint32)

	// 浏览文件名。
	// 我们存储需要加载的文件名字符串总长度，以及每个CU看到的最大
	// 文件索引，以便计算
	// CU->全局表需要多大。
	var fileSize int64
	walkFilenames(ctxt, funcs, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) {
		// 注意，我们使用原始文件名进行查找，但在保存大小时使用扩展文件名
		// 。
		filename := cu.FileTable[i]
		if _, ok := fileOffsets[filename]; !ok {
			fileOffsets[filename] = uint32(fileSize)
			fileSize += int64(len(expandFile(filename)) + 1) // NULL终止
		}

		// 找到我们看到的最大文件索引。
		if cuEntries[cu.PclnIndex] < i+1 {
			cuEntries[cu.PclnIndex] = i + 1 // 存储最大值+1 
		}
	})

	// 计算运行时的大小。cutab变量。
	var totalEntries uint32
	cuOffsets := make([]uint32, len(cuEntries))
	for i, entries := range cuEntries {
		// 注意，cutab是uint32的一部分，因此cu条目的偏移量只是我们目前需要存储的所有cu索引的总和，而不是我们目前存储的字节数。
		cuOffsets[i] = totalEntries
		totalEntries += uint32(entries)
	}

	// 写cutab。
	writeCutab := func(ctxt *Link, s loader.Sym) {
		sb := ctxt.loader.MakeSymbolUpdater(s)

		var off int64
		for i, max := range cuEntries {
			// 写入每CU LUT。
			cu := compUnits[i]
			for j := goobj.CUFileIndex(0); j < max; j++ {
				fileOffset, ok := fileOffsets[cu.FileTable[j]]
				if !ok {
					// 我们正在遍历所有可能的文件索引。一个文件的
					// 可能已被消除死码，尽管它在CU中是有效文件，但在这个二进制文件中它是
					// 不需要的。发生这种情况时，请使用无效的偏移量。
					fileOffset = ^uint32(0)
				}
				off = sb.SetUint32(ctxt.Arch, off, fileOffset)
			}
		}
	}
	state.cutab = state.addGeneratedSym(ctxt, "runtime.cutab", int64(totalEntries*4), writeCutab)

	// 写入文件选项卡。
	writeFiletab := func(ctxt *Link, s loader.Sym) {
		sb := ctxt.loader.MakeSymbolUpdater(s)

		// 编写字符串。
		for filename, loc := range fileOffsets {
			sb.AddStringAt(int64(loc), expandFile(filename))
		}
	}
	state.nfiles = uint32(len(fileOffsets))
	state.filetab = state.addGeneratedSym(ctxt, "runtime.filetab", fileSize, writeFiletab)

	return cuOffsets
}

// generatePctab创建运行时。pctab变量，保存所有
// 已消除重复的pcdata。
func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) {
	ldr := ctxt.loader

	// 0的Pctab偏移量在运行时被视为无效。我们尊重
	// 在运行时开始时只填充一个字节。pctab，
	// 这样，任何实际偏移量都不能为零。
	size := int64(1)

	// 遍历函数，找到偏移量来存储每个pcdata。
	seen := make(map[loader.Sym]struct{})
	saveOffset := func(pcSym loader.Sym) {
		if _, ok := seen[pcSym]; !ok {
			datSize := ldr.SymSize(pcSym)
			if datSize != 0 {
				ldr.SetSymValue(pcSym, size)
			} else {
				// 电脑数据无效，记录为零。
				ldr.SetSymValue(pcSym, 0)
			}
			size += datSize
			seen[pcSym] = struct{}{}
		}
	}
	var pcsp, pcline, pcfile, pcinline loader.Sym
	var pcdata []loader.Sym
	for _, s := range funcs {
		fi := ldr.FuncInfo(s)
		if !fi.Valid() {
			continue
		}
		fi.Preload()
		pcsp, pcfile, pcline, pcinline, pcdata = ldr.PcdataAuxs(s, pcdata)

		pcSyms := []loader.Sym{pcsp, pcfile, pcline}
		for _, pcSym := range pcSyms {
			saveOffset(pcSym)
		}
		for _, pcSym := range pcdata {
			saveOffset(pcSym)
		}
		if fi.NumInlTree() > 0 {
			saveOffset(pcinline)
		}
	}

	// TODO:我们没有理由需要这个变量的生成器，而且它可以移动到载波符号。但是，包含
	// 载波符号的载波符号尚未工作（截至2020年8月）。修复后，
	// 运行时。pctab可能只是一个载体符号。
	writePctab := func(ctxt *Link, s loader.Sym) {
		ldr := ctxt.loader
		sb := ldr.MakeSymbolUpdater(s)
		for sym := range seen {
			sb.SetBytesAt(ldr.SymValue(sym), ldr.Data(sym))
		}
	}

	state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab)
}

// numPCData返回函数信息的PCData符号数。
// NB:在调用此函数之前，必须对有效的funcinfo调用Preload。
func numPCData(ldr *loader.Loader, s loader.Sym, fi loader.FuncInfo) uint32 {
	if !fi.Valid() {
		return 0
	}
	numPCData := uint32(ldr.NumPcdata(s))
	if fi.NumInlTree() > 0 {
		if numPCData < objabi.PCDATA_InlTreeIndex+1 {
			numPCData = objabi.PCDATA_InlTreeIndex + 1
		}
	}
	return numPCData
}

// generateFunctab创建运行时。functab 
// 
// 运行时。functab包含两个内容：
// 
// -pc->func查找表。
// -与pcdata和funcdata交错的func对象数组
func (state *pclntab) generateFunctab(ctxt *Link, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) {
	// 计算表的大小。
	size, startLocations := state.calculateFunctabSize(ctxt, funcs)
	writePcln := func(ctxt *Link, s loader.Sym) {
		ldr := ctxt.loader
		sb := ldr.MakeSymbolUpdater(s)
		// 写入数据。
		writePCToFunc(ctxt, sb, funcs, startLocations)
		writeFuncs(ctxt, sb, funcs, inlSyms, startLocations, cuOffsets, nameOffsets)
	}
	state.pclntab = state.addGeneratedSym(ctxt, "runtime.functab", size, writePcln)
}

// funcData返回FuncInfo的funcData和偏移量。
// 函数数据被写入运行时。每个func 
// 对象后面的functab。这是一个帮助函数，用于使查询FuncInfo对象
// 更干净。
// 
// NB:在调用之前，必须在FuncInfo上调用Preload。
// NB:fdSyms用作暂存空间。
func funcData(ldr *loader.Loader, s loader.Sym, fi loader.FuncInfo, inlSym loader.Sym, fdSyms []loader.Sym) []loader.Sym {
	fdSyms = fdSyms[:0]
	if fi.Valid() {
		fdSyms = ldr.Funcdata(s, fdSyms)
		if fi.NumInlTree() > 0 {
			if len(fdSyms) < objabi.FUNCDATA_InlTree+1 {
				fdSyms = append(fdSyms, make([]loader.Sym, objabi.FUNCDATA_InlTree+1-len(fdSyms))...)
			}
			fdSyms[objabi.FUNCDATA_InlTree] = inlSym
		}
	}
	return fdSyms
}

// CalculateFunctableSize计算pclntab的大小，以及
// 单个func项的输出缓冲区中的偏移量。
func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64, []uint32) {
	ldr := ctxt.loader
	startLocations := make([]uint32, len(funcs))

	// 为pc->func表分配空间。该结构由pc偏移
	// 和func结构的偏移量。之后，我们有一个pc 
	// 值，它标志着二进制文件中最后一个函数的结束。
	size := int64(int(state.nfunc)*2*4 + 4)

	// 现在找到func对象的空间。我们以一种连续的方式进行，
	// 这样我们就可以找到单独的起始位置。
	for i, s := range funcs {
		size = Rnd(size, int64(ctxt.Arch.PtrSize))
		startLocations[i] = uint32(size)
		fi := ldr.FuncInfo(s)
		size += funcSize
		if fi.Valid() {
			fi.Preload()
			numFuncData := ldr.NumFuncdata(s)
			if fi.NumInlTree() > 0 {
				if numFuncData < objabi.FUNCDATA_InlTree+1 {
					numFuncData = objabi.FUNCDATA_InlTree + 1
				}
			}
			size += int64(numPCData(ldr, s, fi) * 4)
			size += int64(numFuncData * 4)
		}
	}

	return size, startLocations
}

// writePCToFunc写入PC->func查找表。
func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, startLocations []uint32) {
	ldr := ctxt.loader
	textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0))
	pcOff := func(s loader.Sym) uint32 {
		off := ldr.SymValue(s) - textStart
		if off < 0 {
			panic(fmt.Sprintf("expected func %s(%x) to be placed at or after textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
		}
		return uint32(off)
	}
	for i, s := range funcs {
		sb.SetUint32(ctxt.Arch, int64(i*2*4), pcOff(s))
		sb.SetUint32(ctxt.Arch, int64((i*2+1)*4), startLocations[i])
	}

	// 表格的最后一项就是结束pc偏移量。
	lastFunc := funcs[len(funcs)-1]
	sb.SetUint32(ctxt.Arch, int64(len(funcs))*2*4, pcOff(lastFunc)+uint32(ldr.SymSize(lastFunc)))
}

// writeFuncs将func结构和pcdata写入运行时。functab。
func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) {
	ldr := ctxt.loader
	deferReturnSym := ldr.Lookup("runtime.deferreturn", abiInternalVer)
	gofunc := ldr.Lookup("go.func.*", 0)
	gofuncBase := ldr.SymValue(gofunc)
	textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0))
	funcdata := []loader.Sym{}
	var pcsp, pcfile, pcline, pcinline loader.Sym
	var pcdata []loader.Sym

	// 编写单个func对象。
	for i, s := range funcs {
		fi := ldr.FuncInfo(s)
		if fi.Valid() {
			fi.Preload()
			pcsp, pcfile, pcline, pcinline, pcdata = ldr.PcdataAuxs(s, pcdata)
		}

		off := int64(startLocations[i])
		// entry UINTPTTR（函数输入PC与textStart的偏移量）
		entryOff := ldr.SymValue(s) - textStart
		if entryOff < 0 {
			panic(fmt.Sprintf("expected func %s(%x) to be placed before or at textStart (%x)", ldr.SymName(s), ldr.SymValue(s), textStart))
		}
		off = sb.SetUint32(ctxt.Arch, off, uint32(entryOff))

		// name int32 
		nameoff, ok := nameOffsets[s]
		if !ok {
			panic("couldn't find function name offset")
		}
		off = sb.SetUint32(ctxt.Arch, off, uint32(nameoff))

		// args int32 
		// TODO:进入函数信息。
		args := uint32(0)
		if fi.Valid() {
			args = uint32(fi.Args())
		}
		off = sb.SetUint32(ctxt.Arch, off, args)

		// 延迟返回
		deferreturn := computeDeferReturn(ctxt, deferReturnSym, s)
		off = sb.SetUint32(ctxt.Arch, off, deferreturn)

		// pcdata 
		if fi.Valid() {
			off = sb.SetUint32(ctxt.Arch, off, uint32(ldr.SymValue(pcsp)))
			off = sb.SetUint32(ctxt.Arch, off, uint32(ldr.SymValue(pcfile)))
			off = sb.SetUint32(ctxt.Arch, off, uint32(ldr.SymValue(pcline)))
		} else {
			off += 12
		}
		off = sb.SetUint32(ctxt.Arch, off, uint32(numPCData(ldr, s, fi)))

		// 将偏移量存储到编译单元的文件表中。
		cuIdx := ^uint32(0)
		if cu := ldr.SymUnit(s); cu != nil {
			cuIdx = cuOffsets[cu.PclnIndex]
		}
		off = sb.SetUint32(ctxt.Arch, off, cuIdx)

		// funcID uint8 
		var funcID objabi.FuncID
		if fi.Valid() {
			funcID = fi.FuncID()
		}
		off = sb.SetUint8(ctxt.Arch, off, uint8(funcID))

		// flag uint8 
		var flag objabi.FuncFlag
		if fi.Valid() {
			flag = fi.FuncFlag()
		}
		off = sb.SetUint8(ctxt.Arch, off, uint8(flag))

		off += 1 // pad 

		// nfuncdata必须是最终条目。
		funcdata = funcData(ldr, s, fi, 0, funcdata)
		off = sb.SetUint8(ctxt.Arch, off, uint8(len(funcdata)))

		// 输出pcdata。
		if fi.Valid() {
			for j, pcSym := range pcdata {
				sb.SetUint32(ctxt.Arch, off+int64(j*4), uint32(ldr.SymValue(pcSym)))
			}
			if fi.NumInlTree() > 0 {
				sb.SetUint32(ctxt.Arch, off+objabi.PCDATA_InlTreeIndex*4, uint32(ldr.SymValue(pcinline)))
			}
		}

		// 将funcdata REF写入go的偏移量。func.*走吧。funcrel.*。
		funcdata = funcData(ldr, s, fi, inlSyms[s], funcdata)
		// 缺少的数据将是^0。请参阅运行时/symtab。开始：数据。
		off = int64(startLocations[i] + funcSize + numPCData(ldr, s, fi)*4)
		for j := range funcdata {
			dataoff := off + int64(4*j)
			fdsym := funcdata[j]
			if fdsym == 0 {
				sb.SetUint32(ctxt.Arch, dataoff, ^uint32(0)) // ^0是“无值”的哨兵
				continue
			}

			if outer := ldr.OuterSym(fdsym); outer != gofunc {
				panic(fmt.Sprintf("bad carrier sym for symbol %s (funcdata %s#%d), want go.func.* got %s", ldr.SymName(fdsym), ldr.SymName(s), j, ldr.SymName(outer)))
			}
			sb.SetUint32(ctxt.Arch, dataoff, uint32(ldr.SymValue(fdsym)-gofuncBase))
		}
	}
}

// pclntab使用
// 运行时函数和文件名信息初始化pclntab符号。

// pclntab为链接输出生成pcln表。
func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
	// Go 1.2的符号表布局记录在golang中。org/s/go12symtab，但
	// 布局和数据自那时起发生了变化。
	// 
	// 截至2020年8月，以下是pclntab的布局：
	// 
	// 。gopclntab/_gopclntab[elf/macho section]
	// 运行时。pclntab 
	// 整个pclntab段的载波符号。
	// 
	// 运行时。pcheader（请参阅：runtime/symtab.go:pcheader）
	// 8字节魔法
	// nfunc[thearch.ptrsize bytes]
	// 。从运行时开始创建funcnametab。pcheader 
	// 到运行时的偏移量。pclntab_从运行时开始。pcheader 
	// 
	// 运行时。funcnametab 
	// /[]以空结尾的函数名列表
	// 
	// 运行时。cutab 
	// 对于i=0#对于j=0#CU[i]
	// uint32偏移量到运行时中使用的最大文件索引。文件名[j]
	// 
	// 运行时的文件选项卡。filetab 
	// /[]以null结尾的文件名字符串
	// 
	// 运行时。pctab 
	// /[]字节的重复数据消除pc数据。
	// 
	// 运行时。functab 
	// 函数表，交替PC和偏移量到func结构[each entry thearch.ptrsize bytes]
	// 结束PC[thearch.ptrsize bytes]
	// func结构，pcdata偏移量，func数据。

	state, compUnits, funcs := makePclntab(ctxt, container)

	ldr := ctxt.loader
	state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0)
	ldr.MakeSymbolUpdater(state.carrier).SetType(sym.SPCLNTAB)
	ldr.SetAttrReachable(state.carrier, true)
	setCarrierSym(sym.SPCLNTAB, state.carrier)

	state.generatePCHeader(ctxt)
	nameOffsets := state.generateFuncnametab(ctxt, funcs)
	cuOffsets := state.generateFilenameTabs(ctxt, compUnits, funcs)
	state.generatePctab(ctxt, funcs)
	inlSyms := makeInlSyms(ctxt, funcs, nameOffsets)
	state.generateFunctab(ctxt, funcs, inlSyms, cuOffsets, nameOffsets)

	return state
}

func gorootFinal() string {
	root := buildcfg.GOROOT
	if final := os.Getenv("GOROOT_FINAL"); final != "" {
		root = final
	}
	return root
}

func expandGoroot(s string) string {
	const n = len("$GOROOT")
	if len(s) >= n+1 && s[:n] == "$GOROOT" && (s[n] == '/' || s[n] == '\\') {
		return filepath.ToSlash(filepath.Join(gorootFinal(), s[n:]))
	}
	return s
}

const (
	BUCKETSIZE    = 256 * MINFUNC
	SUBBUCKETS    = 16
	SUBBUCKETSIZE = BUCKETSIZE / SUBBUCKETS
	NOIDX         = 0x7fffffff
)

// findfunctab生成一个查找表，以快速查找包含
// 函数的pc。请参阅src/runtime/symtab。go:findfunc了解详细信息。
func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) {
	ldr := ctxt.loader

	// 查找最小和最大地址
	min := ldr.SymValue(ctxt.Textp[0])
	lastp := ctxt.Textp[len(ctxt.Textp)-1]
	max := ldr.SymValue(lastp) + ldr.SymSize(lastp)

	// 对于每个子bucket，计算映射到该子bucket的所有符号索引
	// 的最小值。
	n := int32((max - min + SUBBUCKETSIZE - 1) / SUBBUCKETSIZE)

	nbuckets := int32((max - min + BUCKETSIZE - 1) / BUCKETSIZE)

	size := 4*int64(nbuckets) + int64(n)

	writeFindFuncTab := func(_ *Link, s loader.Sym) {
		t := ldr.MakeSymbolUpdater(s)

		indexes := make([]int32, n)
		for i := int32(0); i < n; i++ {
			indexes[i] = NOIDX
		}
		idx := int32(0)
		for i, s := range ctxt.Textp {
			if !emitPcln(ctxt, s, container) {
				continue
			}
			p := ldr.SymValue(s)
			var e loader.Sym
			i++
			if i < len(ctxt.Textp) {
				e = ctxt.Textp[i]
			}
			for e != 0 && !emitPcln(ctxt, e, container) && i < len(ctxt.Textp) {
				e = ctxt.Textp[i]
				i++
			}
			q := max
			if e != 0 {
				q = ldr.SymValue(e)
			}

			// 打印（“%d:[%lld%lld]%s\n”，idx，p，q，s->name）；
			for ; p < q; p += SUBBUCKETSIZE {
				i = int((p - min) / SUBBUCKETSIZE)
				if indexes[i] > idx {
					indexes[i] = idx
				}
			}

			i = int((q - 1 - min) / SUBBUCKETSIZE)
			if indexes[i] > idx {
				indexes[i] = idx
			}
			idx++
		}

		// 填写表格
		for i := int32(0); i < nbuckets; i++ {
			base := indexes[i*SUBBUCKETS]
			if base == NOIDX {
				Errorf(nil, "hole in findfunctab")
			}
			t.SetUint32(ctxt.Arch, int64(i)*(4+SUBBUCKETS), uint32(base))
			for j := int32(0); j < SUBBUCKETS && i*SUBBUCKETS+j < n; j++ {
				idx = indexes[i*SUBBUCKETS+j]
				if idx == NOIDX {
					Errorf(nil, "hole in findfunctab")
				}
				if idx-base >= 256 {
					Errorf(nil, "too many functions in a findfunc bucket! %d/%d %d %d", i, nbuckets, j, idx-base)
				}

				t.SetUint8(ctxt.Arch, int64(i)*(4+SUBBUCKETS)+4+int64(j), uint8(idx-base))
			}
		}
	}

	state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SRODATA, size, writeFindFuncTab)
	ldr.SetAttrReachable(state.findfunctab, true)
	ldr.SetAttrLocal(state.findfunctab, true)
}

// findContainerSyms返回一个位图，按符号编号索引，其中每个容器符号有一个1。
func (ctxt *Link) findContainerSyms() loader.Bitmap {
	ldr := ctxt.loader
	container := loader.MakeBitmap(ldr.NSym())
	// 找到容器符号并标记它们。
	for _, s := range ctxt.Textp {
		outer := ldr.OuterSym(s)
		if outer != 0 {
			container.Set(outer)
		}
	}
	return container
}
